/*
 *   FILE NAME  : wk2xxx_spi.c
 *
 *   WKIC Ltd.
 *   By  Xu XunWei Tech
 *   DEMO Version :2.0 Data:2018-8-08
 *
 *   DESCRIPTION: Implements an interface for the wk2xxx of spi interface
 *
 *   1. compiler warnings all changes
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/freezer.h>
#include <linux/spi/spi.h>
#include <linux/timer.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>

#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/irq.h>
#include <asm/io.h>
#include "wk2xxx_regs.h"
#include "wk2xxx_opts.h"

MODULE_LICENSE("Dual BSD/GPL");

#define PORT_WK2XXX (1)
//#define _DEBUG_WK2XXX

#ifdef _DEBUG_WK2XXX
#define __local_debug(fmt, ...) pr_info("%s:" fmt, __func__, ##__VA_ARGS__)
#else
#define __local_debug(fmt, ...)
#endif /* _DEBUG_WK2XXX */

static DEFINE_MUTEX(wk2xxxs_lock);     //!< race on probe
static DEFINE_MUTEX(wk2xxs_work_lock); //!< work on probe

#define WK2XXX_SCAN_TIMER_INTERVAL   (10) //!< 10ms
#define WK2XXX_MSG_FLAG_START_TX (1 << 0)
struct wk2xxx_invoke_msg
{
    uint32_t flags;
};

struct wk2xxx_port
{
    struct uart_port port;     //!<
    struct spi_device *spi_wk; //!< spi device
    spinlock_t conf_lock;      //!< used for pass value to workqueue
    spinlock_t queue_lock;     //!< used for lock queue
    struct workqueue_struct *workqueue;
    struct work_struct work;
    struct work_struct irqwork;

    int gpio; //!< gpio for read irq state
	struct timer_list	timer; //!< timer for scan the irq gpio

    int suspending;                         //!< not supported yet
    void (*wk2xxx_hw_suspend)(int suspend); //!< not supported yet
    int irq;
    int force_end_work; //!< fast end the workqueue
    struct wk2xxx_invoke_msg msg;

    int state_tx_empty;
};

static struct wk2xxx_port wk2xxxs[NR_PORTS]; /* the chips */

static void wk2xxx_work(struct work_struct *w);
static void wk2xxx_stop_tx(struct uart_port *port);
static int wk2xxx_tx_chars(struct wk2xxx_port *s);
static int wk2xxx_rx_chars(struct wk2xxx_port *s);
static u_int wk2xxx_tx_empty(struct uart_port *port); // or query the tx fifo is not empty?

static int wk2xxx_do_work(struct wk2xxx_port *s)
{
    /* for task queue safe */
    spin_lock(&s->queue_lock);
    if (!s->force_end_work && !work_pending(&s->work) && !freezing(current) && !s->suspending)
    {
        queue_work(s->workqueue, &s->work); //
        spin_unlock(&s->queue_lock);
        __local_debug("appended\n");
        return 1;
    }
    spin_unlock(&s->queue_lock);
    __local_debug("busy\n");
    return 0;
}

static int wk2xxx_do_irqwork(struct wk2xxx_port *s)
{

    spin_lock(&s->queue_lock);
    if (!s->force_end_work && !work_pending(&s->irqwork) && !freezing(current) && !s->suspending)
    {
        queue_work(s->workqueue, &s->irqwork); //
        spin_unlock(&s->queue_lock);
        __local_debug("appended\n");
        return 1;
    }
    spin_unlock(&s->queue_lock);
    __local_debug("busy\n");
    return 0;
}

static void wk2xxx_irqwork(struct work_struct *w)
{
    struct wk2xxx_port *s = container_of(w, struct wk2xxx_port, irqwork);
    int do_max = 10;
    uint8_t glob_irq;
    uint8_t channel_irq;

    __local_debug("enter %ld\n", s->port.iobase);

    while (do_max--)
    {
        glob_irq = wk2xxx_get_glob_int_flag(s->spi_wk);
        __local_debug("%ld, glob int flag is 0x:%0x\n", s->port.iobase,glob_irq);

        if(glob_irq & (1<<(s->port.iobase-1))){
            channel_irq = wk2xxx_get_int_flag(s->spi_wk,s->port.iobase);
            __local_debug("%ld, channel int flag is 0x:%0x\n", s->port.iobase,channel_irq);

            if(channel_irq & (WK2XXX_TFTRIG_INT|WK2XXX_TFEMPTY_INT)){
                // tx fifo trigger
                int cnt = wk2xxx_tx_chars(s);
                if((cnt < WK2XXX_TX_RIGGER_SIZE)&&(channel_irq&WK2XXX_TFTRIG_INT)){
                    // no more padding data, over the int
                    __local_debug("%ld, disable trigger\n", s->port.iobase);
                    wk2xxx_disable_tx_trig(s->spi_wk,s->port.iobase);
                }
            }

            if(channel_irq & (WK2XXX_RXOVT_INT|WK2XXX_RFTRIG_INT)){
                // rx fifo trigger or timeout
                wk2xxx_rx_chars(s);
            }

        }else{

            __local_debug("%ld, glob int done\n", s->port.iobase);
            __local_debug("exit %ld\n", s->port.iobase);
            return ;
        }
    }
    __local_debug("exit %ld\n", s->port.iobase);
}

static int wk2xxx_tx_chars(struct wk2xxx_port *s)
{
    struct circ_buf *xmit = &s->port.state->xmit;
    int cnt = 0;

    __local_debug("enter %ld\n", s->port.iobase);

    if (s->port.x_char)
    {
        __local_debug("%ld, xchar not supported,just drop\n", s->port.iobase);
        //tx = s->port.x_char;
        s->port.icount.tx++;
        s->port.x_char = 0;
    }

    if (!uart_circ_empty(xmit) && !uart_tx_stopped(&s->port))
    {
        // get the available size of the driver
        int i = 0;
        uint8_t buf[256];
        int tx_cp = wk2xxx_get_tx_fifo_cap(s->spi_wk, s->port.iobase);
        if(tx_cp == 256){
            // for fifo function safe
            tx_cp = 255;
        }
        __local_debug("%ld, tx_cp size is %d\n", s->port.iobase, tx_cp);
        for (i = 0; i < tx_cp;)
        {
            buf[i++] = xmit->buf[xmit->tail];
            xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
            if (uart_circ_empty(xmit))
            {
                break;
            }
        }
        if (i > 0)
        {
            __local_debug("%ld, tx to fifo size is %d\n", s->port.iobase, i);
            wk2xxx_write_fifo(s->spi_wk, s->port.iobase,i,buf);
            /* query the up layer push data to circle buffer */
            //tty_flip_buffer_push(&s->port.state->port);
            cnt = i;
            if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS){
		        uart_write_wakeup(&s->port);
            }
        }
    }
    else
    {
        __local_debug("%ld, buffer empty or is stoped\n", s->port.iobase);
    }
    return cnt;
}

static int wk2xxx_rx_chars(struct wk2xxx_port *s)
{
    int cnt = 0;
    int rx_available = 0;
    uint8_t buf[256];
    int flg = TTY_NORMAL;
    int i = 0;

    __local_debug("enter %ld\n", s->port.iobase);

    rx_available = wk2xxx_get_rx_fifo_used(s->spi_wk, s->port.iobase);

    if(rx_available == 256){
        // for lowlevel fifo safe, but not clear the irq
        rx_available = 250;
        pr_info("%ld,rfull\n", s->port.iobase);
    }
    if(rx_available > 0){
        wk2xxx_read_fifo(s->spi_wk, s->port.iobase,rx_available,buf);

        for(i=0;i<rx_available;i++){
            s->port.icount.rx++;
            uart_insert_char(&s->port,0,0x08,(uint)buf[i],flg);
        }

        tty_flip_buffer_push(s->port.state->port.tty->port);
        cnt = rx_available;
        __local_debug("%ld,rx:%d chars\n", s->port.iobase,rx_available);
    }

   return cnt;
}


static void wk2xxx_work(struct work_struct *w)
{
    struct wk2xxx_port *s = container_of(w, struct wk2xxx_port, work);
    struct wk2xxx_invoke_msg msg;
    int tx = 0;

    __local_debug("enter %ld\n", s->port.iobase);
    while (true)
    {
        spin_lock(&s->conf_lock);
        msg = s->msg;
        memset(&s->msg, 0, sizeof(s->msg));
        spin_unlock(&s->conf_lock);
        if (msg.flags == 0)
        {
            break;
        }
        if (msg.flags & WK2XXX_MSG_FLAG_START_TX)
        {
           if(wk2xxx_tx_chars(s)>0){
                tx = 1;
            }
        }
    }
    if(tx){
         __local_debug("%ld get msg start tx, enable trigger\n", s->port.iobase);
         wk2xxx_enable_tx_trig(s->spi_wk,s->port.iobase);
    }
    __local_debug("exit %ld\n", s->port.iobase);
}
static void wk2xxx_spi_timeout(struct timer_list *t)
{
	struct wk2xxx_port *s = from_timer(s, t, timer);

	if (s->port.state) {
        if(gpio_get_value(s->gpio)==0){
            wk2xxx_do_irqwork(s);
        }
		mod_timer(&s->timer, jiffies + msecs_to_jiffies(WK2XXX_SCAN_TIMER_INTERVAL));
	}
}

/*
 *   Return TIOCSER_TEMT when transmitter is not busy.
 */

static u_int wk2xxx_tx_empty(struct uart_port *port) // or query the tx fifo is not empty?
{
    int tx_cp;
    struct wk2xxx_port *s = container_of(port, struct wk2xxx_port, port);

    __local_debug("enter %ld\n", port->iobase);
    
    wk2xxx_do_work(s);
    tx_cp = wk2xxx_get_tx_fifo_cap(s->spi_wk, s->port.iobase);
    
    pr_info("%ld empty get cap %d\n", port->iobase, tx_cp);
    if(tx_cp == 256){
        return 1;
    }
	return 0;
}

static void wk2xxx_set_mctrl(struct uart_port *port, u_int mctrl) // nothing
{
    __local_debug("enter %ld,not supported\n", port->iobase);
}

static u_int wk2xxx_get_mctrl(struct uart_port *port)
{
    __local_debug("enter %ld,use the fix value\n", port->iobase);
    return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
}

/*
 *  interrupts disabled on entry
 */

static void wk2xxx_stop_tx(struct uart_port *port) //
{

    // struct wk2xxx_port *s = container_of(port, struct wk2xxx_port, port);
    __local_debug("enter %ld\n", port->iobase);

    /* need to do with the circle fifo */
    // wk2xxx_disable_tx(s->spi_wk, port->iobase);
    // no need to shutdown lowlevel
}

/*
 * interrupts may not be disabled on entry
 */
static void wk2xxx_start_tx(struct uart_port *port)
{

    struct wk2xxx_port *s = container_of(port, struct wk2xxx_port, port);
    __local_debug("enter %ld\n", port->iobase);

    // wk2xxx_enable_tx(s->spi_wk, s->port.iobase);
    spin_lock(&s->conf_lock);
    s->msg.flags |= WK2XXX_MSG_FLAG_START_TX;
    spin_unlock(&s->conf_lock);

    wk2xxx_do_work(s);
}

/*
 *  Interrupts enabled
 */
static void wk2xxx_stop_rx(struct uart_port *port)
{
    struct wk2xxx_port *s = container_of(port, struct wk2xxx_port, port);

    __local_debug("enter %ld\n", port->iobase);

    wk2xxx_disable_rx(s->spi_wk, port->iobase);
}

/*
 *  No modem control lines
 *
 */
static void wk2xxx_enable_ms(struct uart_port *port) // nothing
{
    __local_debug("enter %ld,not support\n", port->iobase);
}

static void wk2xxx_break_ctl(struct uart_port *port, int break_state)
{
    __local_debug("enter %ld,not support\n", port->iobase);
}


static void wk2xxx_spi_dump_channel_regs(struct wk2xxx_port *s){
    uint8_t glob_regs[WK2XXX_GLOB_REG_DUMP_SIZE];
    uint8_t channel_regs[WK2XXX_CHANNEL_REG_DUMP_SIZE];
    uint8_t i = 0;
    char buf[256]="";
    int offset = 0;

    wk2xxx_dump_glob_regs(s->spi_wk, glob_regs);
    wk2xxx_dump_channel_regs(s->spi_wk,s->port.iobase, channel_regs);

    offset += snprintf(buf+offset,256-offset,"glob:"); 
    for(i=0;i<WK2XXX_GLOB_REG_DUMP_SIZE; i++){
       offset += snprintf(buf+offset,256-offset,"0x%02X,",glob_regs[i]); 
    }

    offset += snprintf(buf+offset,256-offset,"channel %ld:",s->port.iobase); 
    for(i=0;i<WK2XXX_CHANNEL_REG_DUMP_SIZE; i++){
       offset += snprintf(buf+offset,256-offset,"0x%02X,",channel_regs[i]); 
    }

    pr_info("%s\n",buf);
}

/**
 * @brief called by kernel on port open
 * @param port: port
 *
 */
static int wk2xxx_startup(struct uart_port *port) // i
{
    struct wk2xxx_port *s = container_of(port, struct wk2xxx_port, port);
    char queue_mame[12];

    __local_debug("enter %ld\n", port->iobase);

    s->force_end_work = 0;
    snprintf(queue_mame, 12, "wk2xxx-%d", (uint8_t)s->port.iobase);
    /* create workqueue for uart */
    s->workqueue = create_workqueue(queue_mame);
    if (!s->workqueue)
    {
        pr_err("%ld, cannot create workqueue\n", port->iobase);
        return -EBUSY;
    }
    /* initiate work */
    INIT_WORK(&s->work, wk2xxx_work);
    INIT_WORK(&s->irqwork, wk2xxx_irqwork);

    wk2xxx_port_init(s->spi_wk, port->iobase);

    /* clear the circle buffer */
    uart_circ_clear(&s->port.state->xmit);

    udelay(100);
    // dump reg
    wk2xxx_spi_dump_channel_regs(s);
    // startup tiemr
    s->timer.expires = jiffies +  msecs_to_jiffies(WK2XXX_SCAN_TIMER_INTERVAL); 
    add_timer(&s->timer);
    //wk2xxx_do_irqwork(s);my_timer.expires = jiffies + 1*HZ; 
    pr_info("%ld, start timer during %ld\n", port->iobase,msecs_to_jiffies(WK2XXX_SCAN_TIMER_INTERVAL));
    /* shuild call the isr service ones,just indicate the isr already happend. */
    return 0;
}

static void wk2xxx_shutdown(struct uart_port *port)
{
    struct wk2xxx_port *s = container_of(port, struct wk2xxx_port, port);

    __local_debug("enter %ld\n", port->iobase);

    s->force_end_work = 1;
    if (s->workqueue)
    {
        flush_workqueue(s->workqueue);
        destroy_workqueue(s->workqueue);
        s->workqueue = NULL;
    }
    
    del_timer_sync(&s->timer);
    wk2xxx_port_deinit(s->spi_wk, port->iobase);
    return;
}

static void wk2xxx_termios(struct uart_port *port, struct ktermios *termios,
                           struct ktermios *old)
{

    struct wk2xxx_port *s = container_of(port, struct wk2xxx_port, port);
    int baud = 0;
    unsigned short cflag;
    unsigned short lflag;

    cflag = termios->c_cflag;
    lflag = termios->c_lflag;

    __local_debug("enter %ld with cflag:0x%x , lflag:0x%x\n", port->iobase, termios->c_cflag, termios->c_lflag);
    /* only support baudrate change */
    baud = tty_termios_baud_rate(termios);

    wk2xxx_set_baudrate(s->spi_wk, port->iobase, baud);

    tty_termios_encode_baud_rate(termios, baud, baud);

    uart_update_timeout(port, termios->c_cflag, baud);

    __local_debug("%ld set baud:%d\n", port->iobase, baud);
}

static const char *wk2xxx_type(struct uart_port *port)
{
    __local_debug("enter %ld set string wk2xxx\n", port->iobase);
    return port->type == PORT_WK2XXX ? "wk2xxx" : NULL; // this is defined in serial_core.h
}

static void wk2xxx_release_port(struct uart_port *port)
{
    __local_debug("enter %ld not support\n", port->iobase);
}

static int wk2xxx_request_port(struct uart_port *port) // no such memory region needed for wk2xxx
{
    __local_debug("enter %ld not support, return 0\n", port->iobase);
    return 0;
}

static void wk2xxx_config_port(struct uart_port *port, int flags)
{
    __local_debug("enter %ld with flag 0x%0x\n", port->iobase, flags);
    if (flags & UART_CONFIG_TYPE)
    {
        __local_debug("%ld set type\n", port->iobase);
        port->type = PORT_WK2XXX;
    }
}

static int wk2xxx_verify_port(struct uart_port *port, struct serial_struct *ser)
{
    __local_debug("verify pass\n");
    return 0;
}

static struct uart_ops wk2xxx_pops = {
    tx_empty : wk2xxx_tx_empty,
    set_mctrl : wk2xxx_set_mctrl,
    get_mctrl : wk2xxx_get_mctrl,
    stop_tx : wk2xxx_stop_tx,
    start_tx : wk2xxx_start_tx,
    stop_rx : wk2xxx_stop_rx,
    enable_ms : wk2xxx_enable_ms,
    break_ctl : wk2xxx_break_ctl,
    startup : wk2xxx_startup,
    shutdown : wk2xxx_shutdown,
    set_termios : wk2xxx_termios,
    type : wk2xxx_type,
    release_port : wk2xxx_release_port,
    request_port : wk2xxx_request_port,
    config_port : wk2xxx_config_port,
    verify_port : wk2xxx_verify_port,

};

static struct uart_driver wk2xxx_uart_driver = {

    owner : THIS_MODULE,
    major : SERIAL_WK2XXX_MAJOR,
    driver_name : "ttySWK",
    dev_name : "ttysWK",
    minor : MINOR_START,
    nr : NR_PORTS,
    cons : NULL
};

static int uart_driver_registered;

static int wk2xxx_spi_parse_dt(struct device *dev)
{

    int irq_gpio, irq_flags;
    pr_info("%s enter\n", __func__);
    irq_gpio = of_get_named_gpio_flags(dev->of_node, "irq-gpio", 0, (enum of_gpio_flags *)&irq_flags);
    if (!gpio_is_valid(irq_gpio))
    {
        pr_err("%s get irq-gpio failed:%d\n", __func__, irq_gpio);
        return -1;
    }

    return irq_gpio;
}

static int wk2xxx_spi_probe(struct spi_device *spi)
{
    uint8_t i = 0;
    int status, irq_gpio;
    uint8_t dat[1];

    pr_info("%s enter\n", __func__);
    irq_gpio = wk2xxx_spi_parse_dt(&spi->dev);
    if (irq_gpio < 0)
    {
        pr_err("%s parse spi dt failed:%d\n", __func__, irq_gpio);
        return 1;
    }

    status = gpio_request(irq_gpio,NULL);
    if(status < 0){
        pr_err("%s request failed:%d\n", __func__, status);
        return 1;
    }
    gpio_direction_input(irq_gpio);

    wk2xxx_read_global_reg(spi, WK2XXX_GENA, dat);
    if ((dat[0] & 0xf0) != 0x30)
    {
        pr_err("%s read flobal reg failed, GENA=0x%02x, exit!\n", __func__, dat[0]);
        return 1;
    }
    mutex_lock(&wk2xxxs_lock);
    if (!uart_driver_registered)
    {
        uart_driver_registered = 1;
        status = uart_register_driver(&wk2xxx_uart_driver);
        if (status)
        {
            pr_err("%s,couldn't register wk2xxx uart driver,exit\n", __func__);
            mutex_unlock(&wk2xxxs_lock);
            return status;
        }
    }
    pr_info("%s uart driver registed\n", __func__);

    for (i = 0; i < NR_PORTS; i++)
    {
        struct wk2xxx_port *s = &wk2xxxs[i];
        s->spi_wk = spi;
        s->gpio = irq_gpio;
        s->state_tx_empty = 1;
        s->msg.flags = 0;
        // fill the port structure
        s->port.line = i;
        s->port.ops = &wk2xxx_pops;
        s->port.uartclk = WK_CRASTAL_CLK;
        s->port.fifosize = 256;
        s->port.iobase = i + 1;
        s->port.iotype = SERIAL_IO_PORT;
        s->port.flags = ASYNC_BOOT_AUTOCONF;
        s->port.dev = &spi->dev;
        timer_setup(&s->timer, wk2xxx_spi_timeout, 0);
        
        spin_lock_init(&s->conf_lock);
        spin_lock_init(&s->queue_lock);
        status = uart_add_one_port(&wk2xxx_uart_driver, &s->port);
        if (status < 0)
        {
            pr_err("%s,uart_add_one_port failed,i= %d with error %d\n", __func__, i, status);
            mutex_unlock(&wk2xxxs_lock);
            return status;
        }
    }
    pr_info("%s, add %d uart\n", __func__, NR_PORTS);
    mutex_unlock(&wk2xxxs_lock);
    return status;
}

static int wk2xxx_spi_remove(struct spi_device *spi)
{
    int i = 0;
    pr_info("%s start removing\n", __func__);
    mutex_lock(&wk2xxxs_lock);
    for (i = 0; i < NR_PORTS; i++)
    {
        struct wk2xxx_port *s = &wk2xxxs[i];
        pr_info("%s remove uart %d\n", __func__, i);
        uart_remove_one_port(&wk2xxx_uart_driver, &s->port);
    }
    uart_unregister_driver(&wk2xxx_uart_driver);
    mutex_unlock(&wk2xxxs_lock);
    pr_info("%s remove over\n", __func__);
    return 0;
}

static void wk2xxx_spi_shutdown(struct spi_device *spi)
{
    pr_info("%s not supported,just return 0\n", __func__);
}

static const struct of_device_id rockchip_spi_wk2xxx_dt_match[] = {
    {
        .compatible = "wkmic,wk2124spi",
    },
    {},
};
MODULE_DEVICE_TABLE(of, rockchip_spi_wk2xxx_dt_match);

static struct spi_driver wk2xxx_spi_driver = {
    // struct device_driver
    .driver = {
        .name = "wk2xxxspi",
        .bus = &spi_bus_type,
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(rockchip_spi_wk2xxx_dt_match),
    },

    .probe = wk2xxx_spi_probe,
    .remove = wk2xxx_spi_remove,
    .shutdown = wk2xxx_spi_shutdown,
};

static int __init wk2xxx_module_init(void)
{
    int retval;
    retval = spi_register_driver(&wk2xxx_spi_driver);
    pr_info("%s,register spi return v = :%d\n", __func__, retval);
    return retval;
}

static void __exit wk2xxx_module_exit(void)
{
    pr_info("%s,exit\n", __func__);
    return spi_unregister_driver(&wk2xxx_spi_driver);
}

module_init(wk2xxx_module_init);
module_exit(wk2xxx_module_exit);

MODULE_AUTHOR("WKMIC Ltd");
MODULE_DESCRIPTION("wk2xxx generic serial port driver");
MODULE_LICENSE("GPL");
